using System;
using System.Collections;
using System.Data;
using Neo.Core.Util;

namespace Neo.Core
{
    public interface IObjectContext
    {
        /// <summary>
        /// Occurs when a <see cref="IEntityObject"/> has changed successfully.
        /// </summary>
        event EntityObjectChangedHandler EntityObjectChanged;

        /// <summary>
        /// The DataSet containing this ObjectContext&apos;s data.
        /// </summary>
        DataSet DataSet { get; }

        /// <summary>
        /// The DataStore used for persistence.
        /// </summary>
        IDataStore DataStore { get; }

        /// <summary>
        /// If set to true the context will ignore its store when searching for objects.
        /// </summary>
        bool IgnoresDataStore { get; set; }

        /// <summary>
        /// The EntityMapFactory used to retrieve instances of <c>IEntityMap</c> which map 
        /// objects to <c>DataTable</c> and vice versa.
        /// </summary>
        /// <remarks>
        /// While settable it is not advisable to change  the factory after objects have been
        /// loaded/created.
        /// </remarks>
        IEntityMapFactory EntityMapFactory { set; get; }

        /// <summary>
        /// A property collection instance that can be used to store application specific
        /// information in the context.
        /// </summary>
        /// <remarks>
        /// The context completely ignores the contents of the property collection. It is
        /// meant as convenience to avoid having to subclass ObjectContext just because 
        /// some extra properties are needed.
        /// </remarks>
        PropertyCollection ExtendedProperties { get; }

        /// <summary>
        /// Clears and resets the this context.
        /// </summary>
        /// <remarks>
        /// This makes the context effectively forget all object and their changes.
        /// </remarks>
        void Clear();

        /// <summary>
        /// Gets all changes made to the ObjectContext in the form of a diffgram <c>DataSet</c>.
        /// </summary>
        /// <returns>all changes made to the main DataSet</returns>
        DataSet GetChanges();

        /// <summary>
        /// Determines whether there are any changes to objects managed by this context.
        /// </summary>
        /// <returns><c>true</c> if changes have been made</returns>
        bool HasChanges();

        /// <summary>
        /// Marks all changes as having been persisted.
        /// </summary>
        /// <remarks>
        /// Use this in conjunction with GetChanges() to implement alternative
        /// persistence operations, in client/server scenarios for example.
        /// </remarks>
        void AcceptChanges();

        /// <summary>
        /// Persists data changes in this context to the data store.
        /// </summary>
        /// <returns>list of primary keys that were changed during the operation (only for 
        /// native scheme)</returns>
        ICollection SaveChanges();

        /// <summary>
        /// Persists data changes in this context to the data store.
        /// </summary>
        /// <returns>list of primary keys that were changed during the operation (only for 
        /// native scheme)</returns>
        /// <remarks>
        /// This method will work with an existing Enterprise Services transaction, voting 
        /// commit if the save is successfull and abort if any problem occurs.
        /// </remarks>
        ICollection SaveChangesTransactionAware();

        /// <summary>
        /// Registers an observer for column change events.
        /// </summary>
        /// <param name="handler">delegate to be called</param>
        /// <param name="tableName">table name the observer is interested in</param>
        /// <param name="columnName">column the observer is inerested in</param>
        void RegisterForColumnChanges(ColumnChangeHandler handler, string tableName, string columnName);

        /// <summary>
        /// Unregisters and observer for column change events.
        /// </summary>
        /// <param name="handler">delegate that was registered before</param>
        /// <param name="tableName">table name the delegate wishes to stop observing</param>
        /// <param name="columnName">column name the delegate wishes to stop observing</param>
        void UnRegisterForColumnChanges(ColumnChangeHandler handler, string tableName, string columnName);

        /// <summary>
        /// Registers an observer for row change events. Only sent for delete events!
        /// </summary>
        /// <param name="handler">delgate to be called</param>
        /// <param name="tableName">table name the observer is interested in</param>
        void RegisterForRowChanges(RowChangeHandler handler, string tableName);

        /// <summary>
        /// Unregisters an observer for row change events.
        /// </summary>
        /// <param name="handler">delegate that was registered before</param>
        /// <param name="tableName">table name the delegate wishes to stop observing</param>
        void UnRegisterForRowChanges(RowChangeHandler handler, string tableName);

        /// <summary>
        /// Adds a <c>DataRow</c> to the context. This creates a corresponding object if 
        /// necessary.
        /// </summary>
        /// <param name="aRow">row to import</param>
        /// <returns><c>IEntityObject</c> representing the row</returns>
        IEntityObject ImportRow(DataRow aRow);

        /// <summary>
        /// Merges the supplied DataSet with the data in the context. This creates, deletes and 
        /// changes objects as required.
        /// </summary>
        /// <param name="aDataSet"><c>DataSet</c> to be merged</param>
        /// <returns>List of <c>IEntityObject</c>s affected by merge.</returns>
        IList MergeData(DataSet aDataSet);

        /// <summary>
        /// Updates the primary keys for the objects managed by this context.
        /// </summary>
        /// <remarks>
        /// This is used in client/server scenarios where a datastore in a different process
        /// persisted the changes and returned the keys as generated by the database.
        /// </remarks>
        /// <param name="changeTables">list of tables describing the key changes</param>
        void UpdatePrimaryKeys(ICollection changeTables);

        /// <summary>
        /// Updates the primary keys for the objects managed by this context.
        /// </summary>
        /// <remarks>
        /// The context should not have any nested objects while the keys are updated.
        /// </remarks>
        /// <param name="changeTable">table describing the key changes for a <c>DataTable</c>
        /// </param>
        void UpdatePrimaryKeys(PkChangeTable changeTable);

        /// <summary>
        /// Creates a new <c>IEntityObject</c> with all of the required primary key values
        /// </summary>
        /// <remarks>
        /// It is usually better to use the strongly typed API on the factory for the
        /// required entity or the untyped API on <c>ObjectFactory</c>.
        /// </remarks>
        /// <param name="objectType"><c>IEntityObject</c> type to be created</param>
        /// <param name="pkvalues">array holding the correct number of primary key values
        /// or null if the pk scheme generates its own keys, e.g. GUID and native schemes.</param>
        /// <returns>the newly created object</returns>
        IEntityObject CreateObject(Type objectType, object[] pkvalues);

        /// <summary>
        /// Marks object as deleted. This will delete the corresponding row from persistent 
        /// storage when the changes are saved.
        /// </summary>
        /// <param name="eo"><c>IEntityObject</c> to be deleted</param>
        void DeleteObject(IEntityObject eo);

        /// <summary>
        /// Gets all non-deleted objects that the context manages.
        /// </summary>
        /// <returns>all rows registered in the internal dataset</returns>
        ICollection GetAllRegisteredObjects();

        /// <summary>
        /// Looks up the supplied <c>IEntityObject</c> in this context. Throws if the object is not
        /// present in this context.
        /// </summary>
        /// <remarks>
        /// <para>
        /// Use this when you have a set of objects in a parent context and need references
        /// to them in a child context; or vice versa.
        /// </para>
        /// <para>
        /// This method does not trigger the lazy-loading functionality.
        /// </para>
        /// </remarks>
        /// <param name="eo"><c>IEntityObject</c> to find</param>
        /// <returns>instance of supplied <c>IEntityObject</c> in this context</returns>
        IEntityObject GetLocalObject(IEntityObject eo);

        /// <summary>
        /// Creates a new list of <c>EntityObject</c>s based upon the supplied list,
        /// but using its internal representation of those objects.
        /// </summary>
        /// <param name="eoList"></param>
        /// <returns></returns>
        IList GetLocalObjects(IList eoList);

        /// <summary>
        /// Gets all objects managed by this context that are marked for deletion.
        /// </summary>
        /// <returns>list of objects marked for deletion</returns>
        IList GetDeletedObjects();

        /// <summary>
        /// Retrieves an object based on table name and primary key values.
        /// </summary>
        /// <param name="tableName">name of table</param>
        /// <param name="pkvalues">primary keys identifying the object</param>
        /// <returns>object matching primary keys, if found</returns>
        /// <remarks>Any results are merged with this context. Tries to use cached version, if that is available</remarks>
        IEntityObject GetObjectFromTable(string tableName, object[] pkvalues);

        /// <summary>
        /// Retrieves an object based on its <c>ObjectId</c>.
        /// </summary>
        /// <param name="oid">the id of the object</param>
        /// <returns>object matching primary keys, if found</returns>
        /// <remarks>Any results are merged with this context. Tries to use cached version, if that is available</remarks>
        IEntityObject GetObject(ObjectId oid);

        /// <summary>
        /// Retrieves all objects matching the supplied specification
        /// </summary>
        /// <param name="fetchSpec"><c>IFetchSpecification</c> object describing the objects</param>
        /// <returns>matching objects</returns>
        /// <remarks>
        /// This method merges results from the data store, if present, and internal objects.
        /// </remarks>
        IList GetObjects(IFetchSpecification fetchSpec);

        /// <summary>
        /// Gets all objects in other entities that have references to the object passed in.
        /// </summary>
        /// <param name="theObject">the object to start from</param>
        /// <param name="excludeCascades"><t>true</t> to ignore reference from tables into
        /// which the object&apos;s table cascades deletes.</param>
        /// <returns>related objects</returns>
        /// <remarks>
        /// This method is useful to find out whether a given object can be deleted in
        /// the data store.
        /// </remarks>
        IList GetReferencingObjects(IEntityObject theObject, bool excludeCascades);
    }
}